package alibaba.server;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.List;

import alibaba.common.CheckEntry;
import alibaba.common.utils.Base64;
import alibaba.common.utils.CommonUtils;
import alibaba.server.service.BackupService;
import alibaba.server.service.BackupServiceImpl;

import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

public class AlibabaServer implements HttpHandler {

    private static final int SLEEP_INTERVAL = 1000;
	private static final int ALIBABA_SERVER_PORT = 8080;   
	private final BackupService backupService;
	
	public static void main(String[] args) {
	    
		if (args.length != 1) {
			System.err.println("Usage: java -jar AlibabaServer.jar <backup directory name>");
			return;
		}
		
		String backupDirectory = args[0];
		BackupService backupService = new BackupServiceImpl();
		try {
		    // initiate server and check consistency
            if (!backupService.init(backupDirectory)) {
            	System.err.println("Backup directory " + backupDirectory + " is inconsistent");
            	return;
            }
        } catch (IOException e) {
            System.err.println("Unexpected error occured while trying to initiate backup service");
            return;
        }
	
		AlibabaServer alibabaServer = new AlibabaServer(backupService);
		HttpServer server = null;
        try {
            server = alibabaServer.init();
        } catch (IOException e) {
            System.err.println("Unexpected error occured while trying to initiate server");
            return;
        }
		
        // NOTE: Needed for testing purposes
		boolean shutDown = false;
		while (!shutDown) {
		    try {
                Thread.sleep(SLEEP_INTERVAL);
            } catch (InterruptedException e) {
                server.stop(0);
                shutDown = true;
            }
		}
	}
	
	public AlibabaServer(BackupService backupService) {
		this.backupService = backupService;
	}
	
	public HttpServer init() throws IOException {
		InetSocketAddress addr = new InetSocketAddress(ALIBABA_SERVER_PORT);
		HttpServer server;
		server = HttpServer.create(addr, 0);
		server.createContext("/",this);
		server.start();
		System.out.println("Server is listening on port " + ALIBABA_SERVER_PORT);
		return server;
	}
	
	public void handle(HttpExchange exchange) {

	    Headers responseHeaders = exchange.getResponseHeaders();
		responseHeaders.set("Content-Type", "text/plain");
		
		String requestMethod = exchange.getRequestMethod();
		URI uri = exchange.getRequestURI();
		String query = uri.getQuery();
		String path = uri.getPath() == null ? "/" : uri.getPath();
		boolean isDirectoryPath = path.endsWith(String.valueOf(CommonUtils.UNIX_SEPARATOR));
		
		boolean validUserName = true;
		String userName = null;
		String authorizationHeader = exchange.getRequestHeaders().getFirst("Authorization");
		String authorizationHeaderPrefix = "Alibaba ";
		if (authorizationHeader == null || !authorizationHeader.startsWith(authorizationHeaderPrefix)) {
		    validUserName = false;
		} else {
		    userName = authorizationHeader.substring(authorizationHeaderPrefix.length()).trim();
		    if (userName.isEmpty()) {
		        validUserName = false;
		    }
		}
		
		try {
		    if (!validUserName) {
		        exchange.sendResponseHeaders(HttpURLConnection.HTTP_BAD_REQUEST, 0);
		    } else if (requestMethod.equalsIgnoreCase("GET")) {
    			if (query == null) {
    			    if (isDirectoryPath) {
    			        handleListDirectory(exchange,userName, path);
    			    } else {
    			        handleRestoreFile(exchange, userName, path);
    			    }
    			} else if (query.startsWith("snapshot=")) {
    				try {
    					int snapshot = Integer.parseInt(query.substring("snapshot=".length()));
        			    if (isDirectoryPath) {
        			        handleListDirectory(exchange,userName, path, snapshot);
        			    } else {
        			        handleRestoreFile(exchange, userName, path, snapshot);
        			    }
    				} catch (NumberFormatException e) {
    					exchange.sendResponseHeaders(HttpURLConnection.HTTP_BAD_REQUEST, 0);
					}	
    			} else if (query.equalsIgnoreCase("signature") && !isDirectoryPath) {
    				handleGetSignature(exchange, userName, path);
    			} else if (query.equalsIgnoreCase("check") && isDirectoryPath) {
    				handleCheckDirectory(exchange, userName, path);
    			} else {
    				exchange.sendResponseHeaders(HttpURLConnection.HTTP_BAD_REQUEST, 0);
    			}
    		} else if (requestMethod.equalsIgnoreCase("PUT")) {
    		    if (!isDirectoryPath && query == null) {
    		        handleBackupFileData(exchange, userName, path);
    		    } else if (!isDirectoryPath && query.startsWith("signature=")) {
    		        String signature = query.substring("signautre=".length());
    		        handleBackupFileSignature(exchange, userName, path, signature);
    		    } else if (isDirectoryPath && query != null && path.equals(String.valueOf(CommonUtils.UNIX_SEPARATOR))) {
    				try {
    					int snapshot = Integer.parseInt(query);
        			    handleCreateSnapshot(exchange, userName, snapshot);
    				} catch (NumberFormatException e) {
    					exchange.sendResponseHeaders(HttpURLConnection.HTTP_BAD_REQUEST, 0);
					}		     
    		    } else {
                    exchange.sendResponseHeaders(HttpURLConnection.HTTP_BAD_REQUEST, 0);
               }		    
    		} else if (requestMethod.equalsIgnoreCase("DELETE") && isDirectoryPath) {    
    		    if (query != null && path.equals(String.valueOf(CommonUtils.UNIX_SEPARATOR))) {
    				try {
    					int snapshot = Integer.parseInt(query);
        			    handleDeleteSnapshot(exchange, userName, snapshot);
    				} catch (NumberFormatException e) {
    					exchange.sendResponseHeaders(HttpURLConnection.HTTP_BAD_REQUEST, 0);
					}	
    		    } else {
    		    	 exchange.sendResponseHeaders(HttpURLConnection.HTTP_BAD_REQUEST, 0);
    		    }	
    		} else {
    			exchange.sendResponseHeaders(HttpURLConnection.HTTP_BAD_REQUEST, 0);
    		}
		} catch (IOException e) {
		    try {
                exchange.sendResponseHeaders(HttpURLConnection.HTTP_INTERNAL_ERROR, 0);
            } catch (IOException e1) {
                // Not much we can do about this...
                e.printStackTrace();
            }
		}
		exchange.close();
	}

	private void handleBackupFileData(HttpExchange exchange, String userName, String path) throws IOException {
		File sentFile = File.createTempFile("backupTemp", ".tmp");
		InputStream requestBody = exchange.getRequestBody();
        FileOutputStream outputStream = new FileOutputStream(sentFile);
        CommonUtils.readInputStreamIntoOutputStream(requestBody, outputStream);
        outputStream.close();
		boolean check = backupService.backupFileData(userName, path, sentFile);
		if (check) {
			exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0);
		} else {
		    exchange.sendResponseHeaders(HttpURLConnection.HTTP_INTERNAL_ERROR, 0);
		}
	}

	private void handleBackupFileSignature(HttpExchange exchange, String userName, String path, String signature) throws IOException {
	    boolean check = backupService.backupFileSignature(userName, path, signature);
        if (check) {
            exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0);
        } else {
            exchange.sendResponseHeaders(HttpURLConnection.HTTP_NOT_FOUND, 0);
        }
	}
	
	private void handleRestoreFile(HttpExchange exchange, String userName, String path) throws IOException {
		handleRestoreFile(exchange, userName, path, 0);
	}
	
	private void handleRestoreFile(HttpExchange exchange, String userName, String path, int snapshot) throws IOException {
		File restoredFile = backupService.restoreFile(userName, path, snapshot);
		if (restoredFile == null) {
			exchange.sendResponseHeaders(HttpURLConnection.HTTP_NOT_FOUND, 0);
		} else {
			exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0);
			FileInputStream inputStream = new FileInputStream(restoredFile);
			OutputStream responseBody = exchange.getResponseBody();
			CommonUtils.readInputStreamIntoOutputStream(inputStream, responseBody);
			inputStream.close();
		}
	}
	
	private void handleListDirectory(HttpExchange exchange, String userName, String path) throws IOException {
		handleListDirectory(exchange, userName, path, 0);
	}

    private void handleListDirectory(HttpExchange exchange, String userName, String path, int snapshot) throws IOException {
        List<String> listDirectory = backupService.listDirectory(userName, path, snapshot);
        exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0);
        StringBuffer buf = new StringBuffer();
        for (String fileName : listDirectory) {
        	buf.append(fileName);
        	buf.append(CommonUtils.LINE_BREAK);
        }
        byte[] bytes = buf.toString().getBytes();
        InputStream inputStream = new ByteArrayInputStream(bytes);
        OutputStream responseBody = exchange.getResponseBody();
        CommonUtils.readInputStreamIntoOutputStream(inputStream, responseBody);
    }
    
    private void handleCheckDirectory(HttpExchange exchange, String userName, String path) throws IOException {
        List<CheckEntry> filesList = backupService.check(userName, path);
        StringBuilder builder = new StringBuilder();
        for (CheckEntry entry : filesList) {
            builder.append(entry.getFilename());
            builder.append(", ");
            
            for (int snapshotVersion : entry.getSnapshotVersions()) {
                builder.append(snapshotVersion);
                builder.append(" ");
			}
            builder.setLength(builder.length()-1);
            builder.append(", ");
            builder.append(Base64.encodeBytes(entry.getSignature(),Base64.URL_SAFE));
            builder.append(CommonUtils.LINE_BREAK);
        }
        exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0);
        byte[] bytes = builder.toString().getBytes();
        InputStream inputStream = new ByteArrayInputStream(bytes);
        OutputStream responseBody = exchange.getResponseBody();
        CommonUtils.readInputStreamIntoOutputStream(inputStream, responseBody);
    }
    
    private void handleGetSignature(HttpExchange exchange, String userName, String path) throws IOException {
        byte[] signature = backupService.getSignature(userName, path, 0); // TODO
        if (signature == null) {
            exchange.sendResponseHeaders(HttpURLConnection.HTTP_NOT_FOUND, 0);
        } else {
            exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0);
            OutputStream responseBody = exchange.getResponseBody();
            String signatureBase64 = Base64.encodeBytes(signature, Base64.URL_SAFE);
            signatureBase64 += CommonUtils.LINE_BREAK;
            byte[] signatureBytes = signatureBase64.getBytes();
            InputStream inputStream = new ByteArrayInputStream(signatureBytes);
            CommonUtils.readInputStreamIntoOutputStream(inputStream, responseBody);
        }
    }
    
	private void handleCreateSnapshot(HttpExchange exchange, String userName, int snapshot) throws IOException {
	    boolean check = backupService.createSnapshot(userName, snapshot);
        if (check) {
            exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0);
        } else {
            exchange.sendResponseHeaders(HttpURLConnection.HTTP_INTERNAL_ERROR, 0);
        }	
	}

	private void handleDeleteSnapshot(HttpExchange exchange, String userName, int snapshot) throws IOException {
	    boolean check = backupService.deleteSnapshot(userName, snapshot);
        if (check) {
            exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0);
        } else {
            exchange.sendResponseHeaders(HttpURLConnection.HTTP_INTERNAL_ERROR, 0);
        }			
	}
}
